﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using iNet.IO;

namespace iNet.Media.Video
{
	/// <summary>
	/// 提供影片編碼解碼器及相關功能。
	/// </summary>
	public static class VideoCodecs
	{
		#region 欄位
		static readonly Dictionary<IMediaFormat, VideoDecodeSupportStates> _DecodeSupportStates = new Dictionary<IMediaFormat, VideoDecodeSupportStates>();
		static readonly List<IVideoDecodeTaskFactory> _DecodeTaskFactories = new List<IVideoDecodeTaskFactory>();
		static readonly Dictionary<string, List<IVideoDecodeTaskFactory>> _DecodeTaskFactoriesByExtension = new Dictionary<string, List<IVideoDecodeTaskFactory>>(PathComparer.Default);
		static readonly Dictionary<IMediaFormat, List<IVideoDecodeTaskFactory>> _DecodeTaskFactoriesByFormat = new Dictionary<IMediaFormat, List<IVideoDecodeTaskFactory>>();
		static readonly HashSet<IFastMediaFormatRecognizer> _FastFormatRecognizers = new HashSet<IFastMediaFormatRecognizer>();
		static readonly NLog.Logger _Logger = NLog.LogManager.GetLogger("VideoCodecs");
		static volatile int _MinMediaHeaderSize;
		static volatile int _SourceLockTimeout = 5000;
		#endregion
		#region 型別建構子
		static VideoCodecs()
		{
			//建立預設解碼器工廠
			AddDecodeTaskFactory(new Mp4VideoDecodeTaskFactory());

			//建立預設編碼器工廠
			//
		}
		#endregion
		#region AddDecodeTaskFactory
		/// <summary>
		/// 將新的影片解碼工作工廠加入集合。
		/// </summary>
		/// <param name="factory">影片解碼工作工廠。</param>
		/// <exception cref="ArgumentException">factory 是無效的工廠。</exception>
		/// <exception cref="ArgumentNullException">factory 為 Null (Visual Basic 為 Nothing)。</exception>
		public static void AddDecodeTaskFactory(IVideoDecodeTaskFactory factory)
		{
			//確認參數
			if (factory == null)
				throw new ArgumentNullException("factory");

			//取得工廠資訊
			IMediaFormat format = factory.DecodingFormat;
			if (format == null)
				throw new ArgumentException();
			string[] extensions = format.FileNameExtensions;

			//加入集合
			bool isNewFormat;
			List<string> newExtensions = new List<string>();
			lock (_DecodeTaskFactories)
			{
				//加入至列表
				_DecodeTaskFactories.Add(factory);

				//清除快取
				_DecodeSupportStates.Remove(format);

				//加入格式連結集合
				List<IVideoDecodeTaskFactory> list;
				if (!_DecodeTaskFactoriesByFormat.TryGetValue(format, out list))
				{
					list = new List<IVideoDecodeTaskFactory>();
					list.Add(factory);
					_DecodeTaskFactoriesByFormat[format] = list;
					isNewFormat = true;
				}
				else
				{
					list.Add(factory);
					isNewFormat = false;
				}

				//加入副檔名連結集合
				for (int i = extensions.Length - 1; i >= 0; --i)
				{
					string ext = extensions[i];
					if (!_DecodeTaskFactoriesByExtension.TryGetValue(ext, out list))
					{
						list = new List<IVideoDecodeTaskFactory>();
						list.Add(factory);
						_DecodeTaskFactoriesByExtension[ext] = list;
						newExtensions.Add(ext);
					}
					else
						list.Add(factory);
				}

				//加入快速格式識別器集合
				IFastMediaFormatRecognizer recognizer = (factory as IFastMediaFormatRecognizer);
				if (recognizer != null)
				{
					//加入集合
					_FastFormatRecognizers.Add(recognizer);

					//更新最小標頭大小
					int headerSize = recognizer.MediaHeaderSize;
					if (headerSize > _MinMediaHeaderSize)
						_MinMediaHeaderSize = headerSize;
				}
			}

			//引發事件
			if (isNewFormat)
				FormatAdded.RaiseEvent<DataEventArgs<IMediaFormat>>(null, new DataEventArgs<IMediaFormat>(format));
			for (int i = newExtensions.Count - 1; i >= 0; --i)
				FileNameExtensionAdded.RaiseEvent<DataEventArgs<string>>(null, new DataEventArgs<string>(newExtensions[i]));
		}
		#endregion
		#region CreateDecodeTask
		/// <summary>
		/// 建立影片解碼工作。
		/// </summary>
		/// <param name="stream">提供影片的資料流。</param>
		/// <returns>影片解碼工作，Null (Visual Basic 為 Nothing) 表示無法支援指定來源的格式。</returns>
		/// <exception cref="ArgumentNullException">stream 為 Null (Visual Basic 為 Nothing)。</exception>
		public static VideoDecodeTask CreateDecodeTask(Stream stream)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");
			return CreateDecodeTask((object)stream);
		}
		/// <summary>
		/// 建立影片解碼工作。
		/// </summary>
		/// <param name="fileName">影片的檔案名稱。</param>
		/// <returns>影片解碼工作，Null (Visual Basic 為 Nothing) 表示無法支援指定來源的格式。</returns>
		/// <exception cref="ArgumentNullException">fileName 為 Null (Visual Basic 為 Nothing)。</exception>
		public static VideoDecodeTask CreateDecodeTask(string fileName)
		{
			if (fileName == null)
				throw new ArgumentNullException("fileName");
			return CreateDecodeTask((object)fileName);
		}
		static VideoDecodeTask CreateDecodeTask(object source)
		{
			//取得來源
			string fileName = (source as string);
			Stream stream = (source as Stream);

			//建立解碼工作
			byte[] mediaHeader = null;
			lock (_DecodeTaskFactories)
			{
				//取得副檔名
				string extension;
				if (fileName != null)
					extension = Path.GetExtension(fileName);
				else
				{
					FileStream fileStream = (stream as FileStream);
					if (fileStream != null)
						extension = Path.GetExtension(fileStream.Name);
					else
						extension = null;
				}

				//建立媒體儲存
				MediaStorage sourceStorage = (fileName != null ? new MediaStorage(fileName) : new MediaStorage(stream));

				//取得支援該副檔名的工廠
				HashSet<IVideoDecodeTaskFactory> firstFactories = new HashSet<IVideoDecodeTaskFactory>();
				if (extension != null)
				{
					List<IVideoDecodeTaskFactory> list;
					if (_DecodeTaskFactoriesByExtension.TryGetValue(extension, out list))
					{
						for (int i = list.Count - 1; i >= 0; --i)
							firstFactories.Add(list[i]);
					}
				}

				//開啟資料流
				bool ownsStream = (stream == null);
				if (ownsStream)
				{
					int timeout = _SourceLockTimeout;
					Stopwatch watch = (timeout > 0 ? new Stopwatch() : null);
					if (watch != null)
						watch.Start();
					while (true)
					{
						try
						{
							stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
							break;
						}
						catch (Exception ex)
						{
							if (watch != null && watch.ElapsedMilliseconds < timeout)
							{
								System.Threading.Thread.Sleep(300);
								continue;
							}
							_Logger.Error("CreateDecodeTask() - Cannot open file '" + fileName + "'.", ex);
							return null;
						}
					}
				}

				//使用副檔名優先搜尋
				foreach (IVideoDecodeTaskFactory factory in firstFactories)
				{
					try
					{
						//確認格式
						IFastMediaFormatRecognizer fastRecognizer = (factory as IFastMediaFormatRecognizer);
						bool isSupportedFormat;
						if (fastRecognizer != null)
						{
							//讀取標頭
							if (mediaHeader == null)
								mediaHeader = ReadMediaHeader(stream);

							//確認格式
							if (mediaHeader != null && mediaHeader.Length > 0)
								isSupportedFormat = fastRecognizer.IsSupportedFormat(mediaHeader);
							else
								isSupportedFormat = IsSupportedFormat(factory, stream);
						}
						else
							isSupportedFormat = IsSupportedFormat(factory, stream);

						//建立解碼工作
						if (isSupportedFormat)
						{
							VideoDecodeTask task = factory.CreateDecodeTask(sourceStorage) as VideoDecodeTask;
							if (task != null)
								return task;
						}
					}
					catch
					{ }
				}

				//搜尋其他可用的工廠
				List<IVideoDecodeTaskFactory> secondFactories = new List<IVideoDecodeTaskFactory>();
				List<IVideoDecodeTaskFactory> thirdFactories = new List<IVideoDecodeTaskFactory>();
				for (int i = _DecodeTaskFactories.Count - 1; i >= 0; --i)
				{
					IVideoDecodeTaskFactory factory = _DecodeTaskFactories[i];
					if (!firstFactories.Contains(factory))
					{
						if (factory is IFastMediaFormatRecognizer)
							secondFactories.Add(factory);
						else
							thirdFactories.Add(factory);
					}
				}

				//使用其他工廠搜尋
				if (secondFactories.Count > 0)
				{
					//讀取標頭
					if (mediaHeader == null)
						mediaHeader = ReadMediaHeader(stream);

					//建立解碼工作
					for (int i = secondFactories.Count - 1; i >= 0; --i)
					{
						IVideoDecodeTaskFactory factory = secondFactories[i];
						IFastMediaFormatRecognizer recognizer = (factory as IFastMediaFormatRecognizer);
						if (mediaHeader != null)
						{
							if (recognizer.IsSupportedFormat(mediaHeader))
							{
								VideoDecodeTask task = factory.CreateDecodeTask(sourceStorage) as VideoDecodeTask;
								if (task != null)
									return task;
							}
						}
						else if (IsSupportedFormat(factory, stream))
						{
							VideoDecodeTask task = factory.CreateDecodeTask(sourceStorage) as VideoDecodeTask;
							if (task != null)
								return task;
						}
					}
				}
				for (int i = thirdFactories.Count - 1; i >= 0; --i)
				{
					IVideoDecodeTaskFactory factory = thirdFactories[i];
					if (IsSupportedFormat(factory, stream))
					{
						VideoDecodeTask task = factory.CreateDecodeTask(sourceStorage) as VideoDecodeTask;
						if (task != null)
							return task;
					}
				}

				//無法辨識的格式
				return null;
			}
		}
		#endregion
		#region GetDecodingFormatFileNameExtensions
		/// <summary>
		/// 取得所有支援的影片解碼格式檔案副檔名。
		/// </summary>
		/// <returns>支援的影片解碼格式檔案副檔名。</returns>
		public static string[] GetDecodingFormatFileNameExtensions()
		{
			lock (_DecodeTaskFactories)
			{
				return _DecodeTaskFactoriesByExtension.Keys.ToArray<string>();
			}
		}
		#endregion
		#region IsVideoFileName
		/// <summary>
		/// 確認指定的檔案名稱是否為影片檔案名稱。
		/// </summary>
		/// <param name="fileName">指定的檔案名稱。</param>
		/// <returns>True 表示該名稱為影片檔案名稱，False 表示不是。</returns>
		public static bool IsVideoFileName(string fileName)
		{
			//確認資料
			if (string.IsNullOrEmpty(fileName))
				return false;

			//確認檔案名稱
			string extension = Path.GetExtension(fileName);
			if (string.IsNullOrEmpty(extension))
				return false;
			lock (_DecodeTaskFactories)
			{
				return _DecodeTaskFactoriesByExtension.ContainsKey(extension);
			}
		}
		#endregion
		#region IsSupportedFormat
		static bool IsSupportedFormat(IMediaFormatRecognizer recognizer, Stream stream)
		{
			//確認格式
			long initialPosition = -1;
			bool result;
			try
			{
				//儲存目前讀取位置
				initialPosition = stream.Position;

				//確認格式
				result = recognizer.IsSupportedFormat(stream);
			}
			catch (Exception ex)
			{
				_Logger.Error("IsSupportedFormat() - Fail to check media format.", ex);
				result = false;
			}
			finally
			{
				//恢復讀取位置
				try
				{
					stream.Position = initialPosition;
				}
				catch (Exception ex)
				{
					_Logger.Error("IsSupportedFormat() - Fail to reset stream position.", ex);
					result = false;
				}
			}
			return result;
		}
		#endregion
		#region ReadMediaHeader
		static byte[] ReadMediaHeader(Stream stream)
		{
			//確認狀態
			if (_MinMediaHeaderSize <= 0)
				return new byte[0];

			//讀取標頭
			long initialPosition = -1;
			byte[] header;
			try
			{
				//儲存目前讀取位置
				try
				{
					if (stream.CanSeek)
						initialPosition = stream.Position;
				}
				catch
				{ }

				//讀取資料
				byte[] buffer = new byte[_MinMediaHeaderSize];
				int readCount = stream.Read(buffer, 0, _MinMediaHeaderSize);

				//回傳
				if (readCount == _MinMediaHeaderSize)
					return buffer;
				if (readCount > 0)
				{
					byte[] newBuffer = new byte[readCount];
					Array.Copy(buffer, 0, newBuffer, 0, readCount);
					header = newBuffer;
				}
				else
					header = new byte[0];
			}
			catch (Exception ex)
			{
				_Logger.Error("ReadMediaHeader() - Fail to read media header.", ex);
				header = new byte[0];
			}
			finally
			{
				//恢復讀取位置
				try
				{
					if (initialPosition >= 0)
						stream.Position = initialPosition;
				}
				catch (Exception ex)
				{
					_Logger.Error("ReadMediaHeader() - Fail to reset stream position.", ex);
					header = new byte[0];
				}
			}
			return header;
		}
		#endregion
		#region SourceLockTimeout
		/// <summary>
		/// 取得或設定鎖定媒體儲存來源的最長嘗試時間，單位為毫秒。零表示失敗後立即返回，預設值為 5000。
		/// </summary>
		public static int SourceLockTimeout
		{
			get
			{
				return _SourceLockTimeout;
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();
				_SourceLockTimeout = value;
			}
		}
		#endregion
		#region 事件
		/// <summary>
		/// 發生在新的檔案副檔名支援後。
		/// </summary>
		public static event EventHandler<DataEventArgs<string>> FileNameExtensionAdded;
		/// <summary>
		/// 發生在檔案副檔名不再支援後。
		/// </summary>
		public static event EventHandler<DataEventArgs<string>> FileNameExtensionRemoved;
		/// <summary>
		/// 發生在新的影像格式支援後。
		/// </summary>
		public static event EventHandler<DataEventArgs<IMediaFormat>> FormatAdded;
		/// <summary>
		/// 發生在現有的影像格式不再支援後。
		/// </summary>
		public static event EventHandler<DataEventArgs<IMediaFormat>> FormatRemoved;
		#endregion
	}
	#region VideoDecodeSupportStates
	/// <summary>
	/// 表示影片解碼的支援組合。
	/// </summary>
	[Flags]
	public enum VideoDecodeSupportStates
	{
		/// <summary>
		/// 解碼影片。
		/// </summary>
		Video = 0x1,
		/// <summary>
		/// 解碼縮圖。
		/// </summary>
		ThumbnailImage = 0x2,
		/// <summary>
		/// 解碼中繼資料。
		/// </summary>
		Metadata = 0x4,
		/// <summary>
		/// 解碼位元率。
		/// </summary>
		BitRate = 0x8,
		/// <summary>
		/// 解碼影片時間長度。
		/// </summary>
		Duration = 0x10,
		/// <summary>
		/// 解碼視訊框架速度。
		/// </summary>
		VideoFrameRate = 0x20,
		/// <summary>
		/// 解碼視訊像素大小。
		/// </summary>
		VideoPixelSize = 0x40,
	}
	#endregion
}
